package com.demo.web.core;
import com.alibaba.fastjson.JSON;
import com.demo.util.BeanUtils;
import com.demo.util.StringUtils;
import com.demo.web.annotation.RequestMapping;
import com.demo.web.annotation.RequestParam;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * 这个servlet 负责 调度，
 * 所有 tomcat得到的 请求全部转接到这个servlet，然后由这个servlet去调度其他的 带有@Controller的类去完成业务的调度
 * 暂时 只支持 get post 请求，不支持restful风格
 */
@WebServlet("/")
public class DispatcherServlet extends HttpServlet {

    private Map<String, Object> webPathWithBean;
    private Map<String, Object> webPathWithMethod;
    /**
     * 初始化，负责从servletContext中得到 全局存储的信息
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        webPathWithBean = (Map<String,Object>) getServletContext().getAttribute("webPathWithBean");
        webPathWithMethod=((Map<String,Object>) getServletContext().getAttribute("webPathWithMethod"));
    }

    /**
     * 中央控制器，控制请求的转发
     * @param request
     * @param response
     * @throws Exception
     */

    protected void doDispatcher(HttpServletRequest request,HttpServletResponse response)throws Exception{
        Object res = null;
        String uri = request.getRequestURI();// 得到uri
        String method = request.getMethod();//得到请求的方法
        Object args[] = null;
        Object bean = webPathWithBean.get(uri); //得到@Controller的类
        if(bean==null){
            return;
        }
        Method beanMethod =(Method) webPathWithMethod.get(uri);//得到该执行的方法
        /**
         * 进行请求方式 类型的验证 get post delete ...
         *
         */
        RequestMapping requestMapping = beanMethod.getAnnotation(RequestMapping.class);
        if(requestMapping==null){
            return;
        }
        String mappingMethod = requestMapping.type().getMethod();
        if(!(mappingMethod.equalsIgnoreCase("auto")||mappingMethod.equalsIgnoreCase(method))){
            response.getWriter().write("error! 请求方法类型不正确");
            return;
        }
        /*
         * 进行参数的提取处理
         * */
        List<Class<?>> methodParamTypes = new LinkedList<>();//存储方法 参数类型
        List<String>methodParamNames = new LinkedList<>();//存储 方法参数上的 注解，里面有参数名

        Class<?>[] parameterTypes = beanMethod.getParameterTypes();
        Parameter[] parameters = beanMethod.getParameters();

        /*
         * 每一个参数有一个参数类型 和参数名
         * */
        for (Class<?> type : parameterTypes) {
            methodParamTypes.add(type);
        }

        for (Parameter parameter : parameters) {
            RequestParam an = parameter.getAnnotation(RequestParam.class);
            if(an!=null){
                methodParamNames.add(an.value());
            }
        }



        /**
         * 请求方法的不同，获取参数的方法也不同。
         * 差异化处理
         */
        /**
         * TODO：请求方法 的不同，对于参数的处理有哪些差异？
         * 处理参数。。。。。。
         */
        if(method.equalsIgnoreCase("post")){//post 可以使用 json传送数据

            /*
             * 在这里已经得到参数的类型 和参数名
             * 接下来要从request中提取 装配参数
             * */
            String json = getJsonString(request);
            //得到 可能的基本类型的map
            Map<String,String> dataMap = JSON.parseObject(json, Map.class);
            /*
             * 对象类型的装配
             * */
            /*
             * 基本类型的装配
             * */
            List<Object>argsList = new ArrayList<>();
            for(int x=0;x< parameters.length;x++){
                //是基本类型
                if(isBaseType(methodParamTypes.get(x).getName())){
                    Object param = BeanUtils.convertValueByString(methodParamTypes.get(x).getName(),dataMap.get(methodParamNames.get(x)));
                    argsList.add(x,param);
                }else{//对象类型
                    Object obj = JSON.parseObject(json,methodParamTypes.get(x));
                    argsList.add(x,obj);
                }
            }

            /*
             * 把装配好的参数，打成Object[]args,装给 原方法执行的参数。
             * */
            args = argsList.toArray();

        }else if(method.equalsIgnoreCase("get")){
            /**
             * get 只请求数据，不对数据产生影响
             */

            /**
             * get 1，数据从路径中获取 getParameter 方法获取
             */
            /**
             * 2，根据方法的参数，进行数据的封装
             */
            List<Object>argsList = new ArrayList<>();
            for(int x=0;x< parameters.length;x++){
                if(isBaseType(methodParamTypes.get(x).getName())){
                    // 基本数据类型 直接获得
                    Object param = BeanUtils.convertValueByString(methodParamTypes.get(x).getName(), request.getParameter(methodParamNames.get(x)));
                    argsList.add(x,param);
                }else {
//                    对象基本类型，要把其请求的参数信息封装成一个对象
                    Object obj = getObject(methodParamTypes.get(x), request);
                    argsList.add(x,obj);
                }
            }
//            装配参数赋值
            args = argsList.toArray();
        }else {
            response.getWriter().write("restful风格暂时不支持，请使用get或post !");
            return ;
        }
        /**
         * auto 类型默认调用的方法，按照前端请求的方法 进行处理
         */


        //原方法执行
        res = beanMethod.invoke(bean, args);
        /*
         * 将程序执行的结果装配成json
         * 用response将值写回
         * */
        String ret = JSON.toJSONString(res);
        //把返回数据打印 fastjson
        response.getWriter().write(ret);
    }

    /**
     * 通过 传入的 class，和 request对象，把该对象的信息从request中取出来，并封装成一个对象返回
     * @param clazz
     * @param request
     * @param <T>
     * @return
     */
    public <T>T getObject(Class<T> clazz,HttpServletRequest request) throws Exception {
        if(clazz==null){
            return null;
        }
        Object obj = clazz.getDeclaredConstructor().newInstance();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            String fieldName = field.getName();
            String type = field.getType().getName();

            Object fieldValue = BeanUtils.convertValueByString(type,request.getParameter(fieldName));
            String setMethodName = StringUtils.getSetMethod(fieldName);
            Method fieldSetMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
            fieldSetMethod.invoke(obj,fieldValue);
        }
        return (T)obj;
    }

    /**
     * 该方法判断 是否是基本数据类型，赋值检验
     * @return
     */
    public boolean isBaseType(String typeName){
       if(typeName.equals("java.lang.String")||typeName.equals("java.lang.Integer")
       ||typeName.equals("java.lang.Double")||typeName.equals("java.lang.Long")||typeName.equals("java.util.Date")
       ||typeName.equals("java.time.LocalDateTime")){
           return true;
       }
       return false;
    }

    /**
     * 通过request 得到前端输入的json数据，并返回 post请求使用
     * @param request
     * @return
     * @throws IOException
     */
    public String getJsonString(HttpServletRequest request) throws IOException {
        BufferedReader reader = request.getReader();
        StringBuffer sb = new StringBuffer("");
        String temp;
        while ((temp=reader.readLine())!=null){
            sb.append(temp);
        }
        return sb.toString();
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("server error!!!");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("server error!!!");
        }
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("server error!!!");
        }
    }
}
